import re
import socket
import select
import logging
import errno


class CmdHandler(object):

    def __init__(self):
        self.__key_bs = chr(8)
        self.__key_esc = chr(27)
        self.__key_del = chr(127)
        self.__char_del_code = self.__key_bs + ' ' + self.__key_bs
        self.cmdline = ''
        self.escape = False
        self.cursor = 0
        self.output = []
        self.argv = []
        self.hist = []
        self.histidx = 0
        self.maxhist = 500
        self.pattern = None
        self.prompt = '# '
        self.cmdtree = {}
        self.cmdregex = re.compile(r'\b\w+\b|\(.*\)|<.*>')
        self.logger = logging.getLogger('CmdHandler')
        self.output.append(self.prompt)

    def cmd_output(self, data):
        if isinstance(data, str):
            self.output.append(data)
        if isinstance(data, list) or \
                isinstance(data, tuple):
            for d in data:
                if not isinstance(d, str):
                    self.output.append('Error: Invaild data')
                    return
            self.output.extend(data)

    def cmd_register(self, cmd_func):
        cmdline = cmd_func.__doc__
        nodes = [self.cmdtree]
        for match in self.cmdregex.finditer(cmdline):
            part = match.group()
            lastnodes = []
            if not re.match('\(.*\)', part):
                for node in nodes:
                    node.setdefault(part, {})
                    lastnodes.append(node[part])
                nodes = lastnodes
                continue
            for p in part.replace(' ', '').strip(' ()').split('|'):
                for node in nodes:
                    node.setdefault(p, {})
                    node[p]['+ISPARAM'] = None
                    lastnodes.append(node[p])
            nodes = lastnodes
        for node in nodes:
            node['+FUNCTION'] = cmd_func

    def cmd_proc(self, rdata):
        data = list(rdata)
        self.logger.debug('rdata: %s', rdata)
        while len(data):
            ch = data.pop(0)
            if self.escape:
                self.escape = False
                if ch == '[' and len(data) >= 1:
                    key = ch + data.pop(0)
                    if key == '[A':
                        self.hist_previous()
                    elif key == '[B':
                        self.hist_next()
                    elif key == '[C':
                        self.cmd_shift_left()
                    elif key == '[D':
                        self.cmd_shift_right()
                else:
                    break
            elif ch == self.__key_esc:
                self.escape = True
            elif ch == self.__key_del:
                self.cmd_backspace()
            elif ch == '\n' or ch == '\r':
                self.cmd_execute()
            elif ch == '\t':
                self.cmd_complete()
            elif ord(ch) > 31 and ord(ch) <= 127:
                self.cmd_insert(ch)

    def cmd_insert(self, str):
        if len(self.cmdline) == self.cursor:
            self.output.append(str)
            self.cmdline += str
            self.cursor += len(str)
        else:
            code = (self.__key_esc + '[C') * len(self.cmdline[self.cursor:]) + \
                self.__char_del_code * len(self.cmdline)
            self.cmdline = self.cmdline[:self.cursor] + \
                str + self.cmdline[self.cursor:]
            self.cursor += len(str)
            code += self.cmdline + \
                (self.__key_esc + '[D') * len(self.cmdline[self.cursor:])
            self.output.append(code)
        self.logger.debug(
            'cursor: %d, str: %s, cmdline: %s', self.cursor, str, self.cmdline)

    def cmd_match_filter(self):
        licmdparts = self.cmdline.split()
        if len(licmdparts) <= 0:
            return None
        self.argv = []
        cmdnode = self.cmdtree
        if not cmdnode:
            return None
        for i in range(0, len(licmdparts) - 1):
            text = '\n'.join(cmdnode.keys())
            if cmdnode.has_key(licmdparts[i]):
                cmdnode = cmdnode[licmdparts[i]]
                if cmdnode.has_key('+ISPARAM'):
                    self.argv.append(licmdparts[i])
            elif re.findall('^[A-Z<].*', text, re.MULTILINE):
                cmdnode = cmdnode[licmdparts[i]]
                self.argv.append(licmdparts[i])
            else:
                return None
        return (licmdparts[-1], cmdnode)

    def cmd_complete(self):
        match_ret = self.cmd_match_filter()
        if not match_ret:
            return None
        lastpart, cmdnode = match_ret
        if cmdnode.has_key(lastpart):
            cmdnode = cmdnode[lastpart]
            text = '\n'.join(cmdnode.keys())
            mo = re.findall('^[a-z].*', text, re.MULTILINE)
            if mo:
                self.output.append('\n' + ' '.join(mo))
                self.output.append('\n' + self.prompt + self.cmdline)
            if self.cmdline[-1] != ' ':
                self.cmd_insert(' ')
        else:
            text = '\n'.join(cmdnode.keys())
            mo = re.findall('^' + lastpart + '.*', text, re.MULTILINE)
            if len(mo) == 1:
                self.cmd_insert(mo[0][len(lastpart):] + ' ')
            elif len(mo) > 1:
                self.output.append('\n' + ' '.join(mo))
                self.output.append('\n' + self.prompt + self.cmdline)
        self.logger.debug('output: %s', self.output)

    def cmd_reset(self):
        if len(self.output) and len(self.output[-1]):
            if isinstance(self.output[-1], str) and \
                    self.output[-1][-1] != '\n':
                self.output.append('\n')
        self.output.append(self.prompt)
        self.hist_add()
        self.cursor = 0
        self.cmdline = ''

    def cmd_execute(self):
        match_ret = self.cmd_match_filter()
        self.logger.debug(match_ret)
        if not match_ret:
            return None
        lastpart, cmdnode = match_ret
        if cmdnode.has_key(lastpart):
            cmdnode = cmdnode[lastpart]
            if cmdnode.has_key('+ISPARAM'):
                self.argv.append(lastpart)
            if cmdnode.has_key('+FUNCTION'):
                self.output.append('\n')
                cmdnode['+FUNCTION'](self, *self.argv)
                self.cmd_reset()
            return None
        text = '\n'.join(cmdnode.keys())
        mo = re.findall('^[A-Z<].*', text, re.MULTILINE)
        if len(mo) != 1:
            self.output.append("Error: Ambiguous command.")
        else:
            self.argv.append(lastpart)
            cmdnode = cmdnode[mo[0]]
            if cmdnode.has_key('+FUNCTION'):
                self.output.append('\n')
                cmdnode['+FUNCTION'](self, *self.argv)
            else:
                self.output.append("Error: Unknown command.")
        self.cmd_reset()

    def cmd_backspace(self):
        if len(self.cmdline):
            if len(self.cmdline) == self.cursor:
                self.output.append(self.__char_del_code)
                self.cursor -= 1
                self.cmdline = self.cmdline[:-1]
        else:
            if self.cursor > 0:
                code = (__key_esc + '[C') * len(self.cmdline[self.cursor:]) + \
                    __char_del_code * len(self.cmdline)
                self.cmdline = self.cmdline[:(
                    self.cursor - 1)] + self.cmdline[self.cursor:]
                self.cursor -= 1
                code += self.cmdline + \
                    (__key_esc + '[D') * len(self.cmdline[self.cursor:])
                self.output.append(code)

    def cmd_shift_left(self):
        if self.cursor > 0:
            self.cursor -= 1
            self.output.append(__key_esc + '[D')

    def cmd_shift_right(self):
        if self.cursor < len(self.cmdline):
            self.cursor += 1
            self.output.append(__key_esc + '[C')

    def hist_add(self):
        if len(self.hist) != 0:
            if cmp(self.hist[-1], self.cmdline) != 0:
                if len(self.hist) == self.maxhist:
                    del self.hist[0]
                self.hist.append(self.cmdline)
        else:
            self.hist.append(self.cmdline)
        self.histidx = len(self.hist)
        self.logger.debug('histidx: %d', self.histidx)
        self.logger.debug(self.hist)

    def hist_previous(self):
        if self.histidx > 0:
            self.histidx -= 1
            self.output.append(self.__char_del_code * len(self.cmdline))
            self.output.append(self.hist[self.histidx])
            self.cmdline = self.hist[self.histidx]
            self.cursor = len(self.cmdline)

    def hist_next(self):
        if self.histidx < len(self.hist) - 1:
            self.histidx += 1
            self.output.append(self.__char_del_code * len(self.cmdline))
            self.output.append(self.hist[self.histidx])
            self.cmdline = self.hist[self.histidx]
            self.cursor = len(self.cmdline)
